package com.ak.game.service;


import cn.hutool.core.util.ObjectUtil;
import com.ak.game.entity.BettingRecord;
import com.ak.game.entity.PnGameRecord;
import com.ak.game.entity.vo.*;
import com.ak.game.mapper.PnGameRecordMapper;
import com.ak.game.util.AjaxResult;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Component
public class GameScheduler3 {

    private static final Logger log = LoggerFactory.getLogger(GameScheduler3.class);
    /**
     * 静态成员变量 保存当前的游戏状态和倒计时时间
     */
    public static GameStatusRspVo GAME_STATUS = new GameStatusRspVo();

    /**
     * 对局Id
     */
    public static String ROUND_ID = String.valueOf(System.currentTimeMillis());

    /**
     * 对局编号
     */
    public static Integer ROUND_NO;

    /**
     * 开始时间
     */
    public static String START_TIME = String.valueOf(System.currentTimeMillis());

    /**
     * 下注结束时间
     */
    public static String BET_END_TIME;

    /**
     * 当前的对局编号，每天自增
     */
    private static final AtomicInteger DAILY_MARCH_NUMBER = new AtomicInteger(0);

    /**
     * 当前日期的字符串表示
     */
    private static final Calendar CALENDAR = Calendar.getInstance();

    /**
     * 数据库中的最新游戏开始时间
     */
    private static final Calendar CALENDAR_HISTORY = Calendar.getInstance();

    /**
     * 保存游戏的阶段
     */
    public static volatile boolean IS_BETTING_PHASE = true;

    /**
     * 初始化每一个阶段的持续时间
     */
    public static volatile int REMAINING_TIME_IN_SECONDS = 180;

    /**
     * 本局游戏的系统总输赢
     */
    public static BigDecimal WIN = BigDecimal.ZERO;

    /**
     * 保存每一个连接的用户的userId和连接的关系
     */
    public static ConcurrentHashMap<String, ChannelHandlerContext> USER_CTX_MAP = new ConcurrentHashMap<>();

    /**
     * 定时任务类
     */
    private ScheduledExecutorService scheduler;

    /**
     * 下注记录类
     */
    @Autowired
    private BettingRecord bettingRecord;

    /**
     * 游戏service层
     */
    @Autowired
    private BettingService bettingService;

    /**
     * 接口api连接的service层
     */
    @Autowired
    private RemoteWebSocketClientService clientService;

    /**
     * PnGameRecord的service层
     */
    @Autowired
    private PnGameRecordMapper gameRecordMapper;

    /**
     * 创建Random对象用于生成开奖结果
     */
//    private final Random random = new Random();

    /**
     * ArrayList数据容器，封装所有用户的下注结果
     */
//    private List<BetRspVo> betRspVoList = CollectionUtil.newArrayList();

    /**
     * ArrayList数据容易，用来储存所有用户下注请求的CompletableFuture对象
     */
//    List<CompletableFuture<BetRspVo>> futures = new ArrayList<>();

    /**
     * ConcurrentHashMap数据容器，封装所有用户的下注结果，和各自连接的通道代理上下文
     */
//    public static ConcurrentHashMap<BetRspVo, ChannelHandlerContext> results = new ConcurrentHashMap<>();

    /**
     * 创建一个ConcurrentHashMap来存储BetReqVo和ChannelHandlerContext的对应关系
     */
    public static ConcurrentHashMap<String, ChannelHandlerContext> reqCtxMap = new ConcurrentHashMap<>();

    /**
     * 前15条的对局记录
     */
    public static List<PnGameRecord> gameRecordList;

    /**
     * 调用定时任务配置方法
     */
    @PostConstruct
    private void startGame() {
        initRoundNo();
        // 单线程定时器交替执行下注和开奖阶段的任务
        scheduler = Executors.newScheduledThreadPool(1);
        generateRoundIdAndTimes();
        startBettingPhase();
    }

    /**
     * 初始化对局编号
     */
    private void initRoundNo() {
        LambdaQueryWrapper<PnGameRecord> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(PnGameRecord::getGameId,"game_self_3");
        lambdaQueryWrapper.eq(PnGameRecord::getGameTime,"3");
        lambdaQueryWrapper.orderByDesc(PnGameRecord::getStartTime);
        lambdaQueryWrapper.last("limit 15");
        List<PnGameRecord> pnGameRecords = gameRecordMapper.selectList(lambdaQueryWrapper);
        if (ObjectUtil.isNotEmpty(pnGameRecords)) {
            gameRecordList = pnGameRecords;
            PnGameRecord pnGameRecord = pnGameRecords.get(0);
            Date startTime = pnGameRecord.getStartTime();
            DAILY_MARCH_NUMBER.set(pnGameRecord.getRoundNo());
            CALENDAR_HISTORY.setTime(startTime);
            CALENDAR.setTime(new Date());
        } else {
            gameRecordList = new ArrayList<>();
            DAILY_MARCH_NUMBER.set(0);
            CALENDAR_HISTORY.setTime(new Date());
            CALENDAR.setTime(new Date());
        }
    }


    private void startBettingPhase() {
        // 开始下注阶段
        scheduler.scheduleAtFixedRate(this::runBettingOrDrawingPhase, 0, 1, TimeUnit.SECONDS);
    }

    private void runBettingOrDrawingPhase() {
        if (IS_BETTING_PHASE) {
            // 打印下注阶段信息
//            System.out.println("当前阶段: 下注, 剩余时间: " + REMAINING_TIME_IN_SECONDS);
            GAME_STATUS.setCountdown(REMAINING_TIME_IN_SECONDS);
            REMAINING_TIME_IN_SECONDS--;
            // 立即执行
            if (REMAINING_TIME_IN_SECONDS == 179) {
                sendGameStatus();
            }
            if (REMAINING_TIME_IN_SECONDS <= 0) {
                // 生成下注结束时间
                BET_END_TIME = String.valueOf(System.currentTimeMillis());
                // 结束下注阶段，开始开奖阶段
                IS_BETTING_PHASE = false;
                GAME_STATUS.setStatus(IS_BETTING_PHASE);
//                sendGameStatus();
                REMAINING_TIME_IN_SECONDS = 20;
                log.info("结束下注阶段，开始开奖阶段");
            }
        } else {
            // 打印开奖阶段信息
//            System.out.println("当前阶段: 开奖, 剩余时间: " + REMAINING_TIME_IN_SECONDS);
            GAME_STATUS.setCountdown(REMAINING_TIME_IN_SECONDS);
            REMAINING_TIME_IN_SECONDS--;
            // 立即执行
            if (REMAINING_TIME_IN_SECONDS == 19) {
//                sendGameStatus();
                revealResults();
            }
            if (REMAINING_TIME_IN_SECONDS <= 0) {
                generateRoundIdAndTimes();
                // 结束开奖阶段，重新开始下注阶段
                IS_BETTING_PHASE = true;
                GAME_STATUS.setStatus(IS_BETTING_PHASE);
                REMAINING_TIME_IN_SECONDS = 180;
                // 生成对局id和游戏开始时间
                String start = String.valueOf(System.currentTimeMillis());
                ROUND_ID = start;
                START_TIME = start;
                log.info("结束开奖阶段，重新开始下注阶段");
            }
        }
    }

    public void sendGameStatus() {
        Map<String, Object> gameStatus = Map.of("betCountdown",180,"drawCountdown",20);
        for (Map.Entry<String, ChannelHandlerContext> entry : USER_CTX_MAP.entrySet()){
            ChannelHandlerContext ctx = entry.getValue();
            ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(AjaxResult.success(gameStatus,"1002"))));
        }
    }

    public void shutdown() {
        scheduler.shutdown();
    }

    private void checkAndResetDailyMatchNumber() {
        int currentDayOfMonth = CALENDAR.get(Calendar.DAY_OF_MONTH);
        if (currentDayOfMonth != CALENDAR_HISTORY.get(Calendar.DAY_OF_MONTH)) {
            DAILY_MARCH_NUMBER.set(0);
        }
    }

    private void generateRoundIdAndTimes() {
        initRoundNo();
        checkAndResetDailyMatchNumber();
        ROUND_NO = DAILY_MARCH_NUMBER.incrementAndGet();
    }


    /**
     * 揭晓开奖结果
     */
    private void revealResults() {
        // 循环开10次奖结果0-9，分别计算10个数字的总输赢，取其中一次。
        // 获取所有投注记录
        Map<BetReqVo, ChannelHandlerContext> betReqVos = bettingRecord.getBets3();
        // 保存循环尝试的下注结果，key为0-9的开奖数字，value为下注结果
        Map<Integer, List<BetRspVo>> resultMap = new HashMap<>();
        try {
            // 循环开奖
            for (int i = 0; i < 10; i++) {
                int result = i;
                List<CompletableFuture<BetRspVo>> futures = new ArrayList<>();
                for (Map.Entry<BetReqVo, ChannelHandlerContext> entry : betReqVos.entrySet()) {
                    // 下注数据
                    BetReqVo betReqVo = entry.getKey();
                    ChannelHandlerContext ctx = entry.getValue();
                    // 将BetReqVo和ChannelHandlerContext的对应关系保存到reqCtxMap中
                    reqCtxMap.put(betReqVo.getUserId(), ctx);
                    // 启动异步任务并收集到futures列表中
                    CompletableFuture<BetRspVo> future = bettingService.asyncGentleBet(betReqVo, result);
                    futures.add(future);
                }
                CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
                // 当所有任务完成时，收集结果并统一处理
                combinedFuture.thenRun(() -> {
                    // betRspVoList为此开奖号码所有用户的输赢情况返回值
                    List<BetRspVo> betRspVoList = futures.stream()
                            .map(CompletableFuture::join)
                            .collect(Collectors.toList());
                    resultMap.put(result, betRspVoList);
                });
                // 阻塞致combinedFuture的所有异步任务已完成
                combinedFuture.get();
            }
            Map<Integer, BigDecimal> totalWinMap = new HashMap<>();
            for (Map.Entry<Integer, List<BetRspVo>> integerListEntry : resultMap.entrySet()) {
                // 总返回的值
                BigDecimal totalWin = integerListEntry.getValue().stream().map(BetRspVo::getWin).reduce(BigDecimal.ZERO, BigDecimal::add);
                // 总下注
                BigDecimal totalAmount = integerListEntry.getValue().stream().map(BetRspVo::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
                BigDecimal subtract = totalAmount.subtract(totalWin);
                if (subtract.compareTo(BigDecimal.ZERO) >= 0) {
                    totalWinMap.put(integerListEntry.getKey(), totalAmount.subtract(totalWin));
                }
            }
            // 转换为List<Map.Entry>
            List<Map.Entry<Integer, BigDecimal>> list = new ArrayList<>(totalWinMap.entrySet());
            Collections.shuffle(list);
            // 使用Stream API的sorted()方法，降序排序，系统盈利从多到少
            list.sort(Map.Entry.comparingByValue(Comparator.reverseOrder()));
            // 获取value值最大的条目，
            // 根据杀率来判定开奖号码，totalWinMap为系统赢的开奖号码，key为开奖号码，value为系统总盈利
            List<BetRspVo> betRspVoListFinal = new ArrayList<>();
            // 杀率0-1的浮点小数，精度为0.01
            Double winRate = BettingService.WIN_RATE;
            Double i = Double.valueOf((double) 1 / list.size());
            int v = (int) (winRate / i);
            // 开奖号码
            Integer type = 0;
            if (v == 0) {
                type = list.get(0).getKey();
                betRspVoListFinal = resultMap.get(list.get(0).getKey());
            } else {
                type = list.get(v - 1).getKey();
                betRspVoListFinal = resultMap.get(list.get(v - 1).getKey());
            }
            List<AllBetReqVo> allBet = new ArrayList<>();
            for (BetRspVo betRspVo : betRspVoListFinal) {
                if (betRspVo.getWin().compareTo(BigDecimal.ZERO) == 0) {
                    WIN = WIN.add(betRspVo.getAmount());
                } else {
                    WIN = WIN.subtract(betRspVo.getWin().subtract(betRspVo.getAmount()));
                }
                AllBetReqVo allBetReqVo = new AllBetReqVo()
                        .setAmount(betRspVo.getAmount())
                        .setWin(betRspVo.getWin())
                        .setMultiple(0)
                        .setType(betRspVo.getBetType())
                        .setUser_id(betRspVo.getUserId())
                        .setRound_id(ROUND_ID);
                allBet.add(allBetReqVo);
                ChannelHandlerContext ctx = reqCtxMap.get(betRspVo.getUserId());
                if (ObjectUtil.isNotNull(ctx) && ctx.channel().isActive()) {
                    bettingService.returnMessageAjax(AjaxResult.success(betRspVo, "1005"), ctx);
                }
            }
            // 开奖记录返回给接口api端
            BetResultInfoReqVo betResultInfoReqVo = new BetResultInfoReqVo()
                    .setBet_end_time(BET_END_TIME)
                    .setStart_time(START_TIME)
                    .setRound_id(ROUND_ID)
                    .setEnd_time(String.valueOf(Long.parseLong(START_TIME) + (60 * 1000)))
                    .setAward_multiple(BigDecimal.ZERO)
                    .setWin(WIN)
                    .setAll_bet(allBet)
                    .setId("game_self_3")
                    .setRound_no(ROUND_NO)
                    .setType(type)
                    .setGame_time("3");
            for (Map.Entry<String, ChannelHandlerContext> entry : USER_CTX_MAP.entrySet()) {
                ChannelHandlerContext ctx = entry.getValue();
                bettingService.returnMessageAjax(AjaxResult.success(betResultInfoReqVo, "1003"), ctx);
            }
            Map<String, Object> map = Map.of("msg_id", 104, "game", betResultInfoReqVo);
            String jsonString = JSONObject.toJSONString(map);
            Boolean response = true;
            do {
                response = Boolean.valueOf(clientService.sendMessage(jsonString).get(10, TimeUnit.SECONDS).toString());
            } while (!response);
            log.info("3分钟对局的定时任务资源，bets3={},reqCtxMap={},USER_CTX_MAP={},betReqVos={},gameRecordList={},FUTURES={},resultMap={}", BettingRecord.bets3.size(), reqCtxMap.size(), USER_CTX_MAP.size(), betReqVos.size(), gameRecordList.size(), RemoteWebSocketClientService.FUTURES, resultMap.size());
        } catch (InterruptedException | ExecutionException |
                 TimeoutException e) {
            throw new RuntimeException(e);
        } finally {
            // 清空所有数据容器
            BettingRecord.bets3.clear();
            reqCtxMap.clear();
            betReqVos.clear();
            resultMap.clear();
            WIN = BigDecimal.ZERO;
        }
    }
}
